/**
 * 微信小游戏高级组合图表组件 (K线/美国线 + 动态指标柱状图 + 技术指标折线图)
 * 功能：支持柱状图数据源切换、数据归一化、负值向上显示、无高低价字段适配
 */
export default class AdvancedComboChart {
  constructor(options = {}, textRenderer) {
    // --- 1. 配置项 ---
    this.config = {
      // 布局与尺寸
      x: options.x || 0,
      y: options.y || 150,
      width: options.width || 1454,
      height: options.height || 484,
      padding: options.padding || { top: 10, right: 60, bottom: 0, left: 30 },

      // 样式
      backgroundColor: options.backgroundColor || '#1e1e1e',
      gridColor: options.gridColor || '#2a2a2a',
      textColor: options.textColor || '#9c9c9c',

      // 数据字段映射 (根据你的数据结构)
      dataFields: {
        date: options.dataFields?.date || 'trade_date',
        open: options.dataFields?.open || 'open_hfq',
        close: options.dataFields?.close || 'close_hfq',
        high: options.dataFields?.high || 'high_hfq', // 设为null表示数据中没有此字段
        low: options.dataFields?.low || 'low_hfq',   // 设为null表示数据中没有此字段
        volume: options.dataFields?.volume || 'vol',
        macd: options.dataFields?.macd || 'macd_hfq',
        kdj: options.dataFields?.kdj || 'kdj_hfq',
        boll: options.dataFields?.boll || 'boll_lower_hfq',
      },

      // K线/美国线图配置
      kLine: {
        height: 0, // 动态计算
        barSpacing: 0.15,
        upColor: options.kLine?.upColor || '#ef5350',
        downColor: options.kLine?.downColor || '#26a69a',
      },

      // 指标柱状图配置
      barChart: {
        height: 0, // 动态计算
        barWidth: 12,
        barSpacing: 1,
        defaultIndicator: 'volume',
        availableIndicators: {
          volume: { name: '成交量', field: 'vol' },
          macd: { name: 'MACD', field: 'macd_hfq' },
          kdj: { name: 'KDJ', field: 'kdj_hfq' },
          boll: { name: 'BOLL', field: 'boll_lower_hfq' },
        },
        positiveColor: options.barChart?.positiveColor || '#81FCAE',
        negativeColor: options.barChart?.negativeColor || '#FF6B6B',
      },

      // 技术指标配置
      indicators: {
        height: 0, // 动态计算
        allLines: ["MA5", "MA10", "MA20", "MA30", "MA60", "MA90", "MA250"],
        defaultVisible: ["MA5", "MA10", "MA20", "MA30"],
        visibleLines: options.indicators?.visibleLines || ["MA5", "MA10", "MA20", "MA30"],
        lineColors: {
          MA5: '#FFA726', MA10: '#66BB6A', MA20: '#42A5F5', MA30: '#EC407A',
          MA60: '#AB47BC', MA90: '#5C6BC0', MA250: '#26C6DA'
        },
        lineWidth: 1.5,
      },

      // 数据加载配置
      initialDisplayCount: 50,
      maxDataCount: 260,

      // 交互配置
      crosshair: { color: 'rgba(255, 255, 255, 0.3)' },

      // 【新增】Y轴标签显示开关，默认为true（显示）
      showYAxisLabels: options.showYAxisLabels || false,
    };

    this.textRenderer = textRenderer;

    // --- 2. 内部状态 ---
    this.allData = [];
    this.displayedData = [];
    this.dataBounds = {}; // 初始化为空对象

    this.currentBarChartIndicator = this.config.barChart.defaultIndicator;

    // 【修改】移除旧的 tooltip 配置，增加浮动标签状态
    this.floatingLabel = {
      visible: false,
      x: 0,
      y: 0,
      text: '',
      bgColor: 'rgba(0, 0, 0, 0.75)',
      textColor: '#ffffff',
    };

    // 交互状态
    this.touch = { x: null, y: null };
    this.isDragging = false;
    this.dragStartX = 0;
    this.dragStartOffsetX = 0;
    this.offsetX = 0;
    this.hoveredCandle = null;

    // 【新增】用于区分点击和滑动的状态
    this.touchStartTime = 0;
    this.hasMoved = false;

    this.callbacks = {
      onDataUpdate: options.onDataUpdate || null,
      // 【新增】点击事件回调
      onBarClick: options.onBarClick || null,
    };

    // --- 3. 初始化 ---
    this._calculateAreaHeights();
    this.init();
  }

  init() {
    // 确保在任何情况下都有默认的边界，防止首次绘制时崩溃
    this._setDefaultBounds();

    if (this.config.data && this.config.data.length > 0) {
      this.updateData(this.config.data);
    }
  }

  // 【新增】专门绘制浮动标签的方法
  _drawFloatingLabel(ctx) {
    const label = this.floatingLabel;
    ctx.save();

    // 设置字体
    ctx.font = '24px sans-serif'; // 字体稍微大一点
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';

    const lines = label.text.split('\n');

    // 【样式优化】增加内边距和行高
    const padding = 10; // 四周内边距从8增加到10
    const lineHeight = 36; // 文字行高从18增加到20

    const maxTextWidth = Math.max(...lines.map(line => ctx.measureText(line).width));
    const boxWidth = maxTextWidth + padding * 2;
    const boxHeight = lines.length * lineHeight + padding * 2;

    let boxX = label.x - boxWidth / 2;
    let boxY = label.y - boxHeight / 2;

    boxX = Math.max(0, Math.min(boxX, this.config.width - boxWidth));
    boxY = Math.max(0, Math.min(boxY, this.config.height - boxHeight));

    // 绘制背景
    ctx.fillStyle = label.bgColor;
    ctx.fillRect(boxX, boxY, boxWidth, boxHeight);

    // 绘制文本
    ctx.fillStyle = label.textColor;
    lines.forEach((line, index) => {
      // 【样式优化】使用新的行高计算文字Y坐标
      const textY = boxY + padding + (index + 0.5) * lineHeight;
      ctx.fillText(line, label.x, textY);
    });

    ctx.restore();
  }

  _setDefaultBounds() {
    this.dataBounds = {
      kLine: { min: 0, max: 100 },
      barChart: { min: -100, max: 100 },
      indicators: { min: 0, max: 100 },
    };
  }

  _calculateAreaHeights() {
    let totalHeight = this.config.height;
    // 调整比例：K线占最大，指标次之，柱状图最小
    const kLineHeightRatio = 0.4; // K线区域 60%
    const indicatorHeightRatio = 0.3; // 指标区域 25%
    const barChartHeightRatio = 0.3; // 柱状图区域 15%

    this.config.kLine.height = totalHeight * kLineHeightRatio;
    this.config.indicators.height = totalHeight * indicatorHeightRatio;
    this.config.barChart.height = totalHeight * barChartHeightRatio;
  }

  // --- 4. 数据处理 ---
  updateData(newDataArray) {
    this.allData = newDataArray.slice(0, this.config.maxDataCount);
    this.displayedData = this.allData.slice(0, this.config.initialDisplayCount);
    this._calculateDataBounds();
    this._checkAutoScroll();
  }

  appendData(newDataPoint) {
    // 2. 将新数据同时添加到 allData 和 displayedData
    this.allData.push(newDataPoint);
    this.displayedData.push(newDataPoint);

    // 3. 重新计算数据边界（非常重要！）
    this._calculateDataBounds();

    // 4. 执行自动滚动，确保新数据可见
    this._checkAutoScroll();
  }

  _calculateDataBounds() {
    // 如果没有数据，使用默认边界
    if (this.displayedData.length === 0) {
      this._setDefaultBounds();
      return;
    }

    const fields = this.config.dataFields;
    // 使用开收盘价计算K线边界
    const prices = this.displayedData.flatMap(d => [d[fields.open], d[fields.close]]);
    this.dataBounds.kLine = { min: Math.min(...prices), max: Math.max(...prices) };

    // 计算柱状图边界
    this._calculateBarChartBounds();

    // 计算指标折线边界
    const indicatorKeys = this.config.indicators.visibleLines;
    const indicatorValues = this.displayedData.flatMap(d =>
      indicatorKeys.map(key => {
        const mappedKey = key.toLowerCase().replace('ma', 'ma_hfq_');
        return d[mappedKey];
      }).filter(v => v !== undefined && v !== null)
    );
    this.dataBounds.indicators = { min: Math.min(...indicatorValues), max: Math.max(...indicatorValues) };

    // 为每个边界留出5%的边距
    Object.keys(this.dataBounds).forEach(key => {
      const range = this.dataBounds[key].max - this.dataBounds[key].min;
      if (range > 0) {
        this.dataBounds[key].min -= range * 0.05;
        this.dataBounds[key].max += range * 0.05;
      }
    });
  }

  _calculateBarChartBounds() {
    const indicatorField = this.config.barChart.availableIndicators[this.currentBarChartIndicator].field;
    const values = this.displayedData.map(d => d[indicatorField]).filter(v => v !== undefined && v !== null);

    if (values.length === 0) {
      this.dataBounds.barChart = { min: -1, max: 1 };
      return;
    }

    const max = Math.max(...values.map(Math.abs));
    const min = -max;
    this.dataBounds.barChart = { min, max };
  }

  _checkAutoScroll() {
    if (this.displayedData.length === 0) {
      this.offsetX = 0;
      return;
    }

    const barWidth = this.getBarWidth();
    const totalWidth = this.displayedData.length * barWidth;

    // 【核心修改】如果总宽度超过画布宽度，则进行自动滚动
    if (totalWidth > this.config.width) {
      // 计算偏移量，使得最后一根K线的中心线位于画布的右边缘
      // 这样可以保证最新的数据总是可见的
      this.offsetX = this.config.width - totalWidth + barWidth / 2;
    } else {
      // 如果数据还不多，可以全部显示在屏幕内，则居左显示
      this.offsetX = 0;
    }
  }

  // --- 5. 核心绘制方法 ---
  draw(ctx) {
    if (!ctx) return;
    ctx.save();

    // 绘制背景
    ctx.fillStyle = this.config.backgroundColor;
    ctx.fillRect(this.config.x, this.config.y, this.config.width, this.config.height);

    let currentY = this.config.y;

    if (this.config.kLine.height > 0) {
      this._drawKLineArea(ctx, currentY);
      currentY += this.config.kLine.height;
    }
    if (this.config.barChart.height > 0) {
      this._drawIndicatorArea(ctx, currentY);
      currentY += this.config.barChart.height;
    }
    if (this.config.indicators.height > 0) {
      this._drawBarChartArea(ctx, currentY);
    }

    if (this.config.crosshair) this._drawCrosshair(ctx);

    ctx.restore();

    if (this.floatingLabel.visible) {
      this._drawFloatingLabel(ctx);
    }
  }

  _drawKLineArea(ctx, y) {
    const height = this.config.kLine.height;
    this._drawGrid(ctx, y, height);
    if (this.dataBounds.kLine && this.config.showYAxisLabels) {
      this._drawYAxisLabels(ctx, y, height, this.dataBounds.kLine, 2);
    }

    ctx.save();
    ctx.beginPath();
    ctx.rect(this.config.x, y, this.config.width, height);
    ctx.clip();
    ctx.translate(this.config.x + this.offsetX, y);

    if (this.displayedData.length === 0) {
      ctx.restore();
      return;
    }

    const fields = this.config.dataFields;
    const barWidth = this.getBarWidth();
    const hasRealHighLow = fields.high && fields.low; // 检查是否有真实的高低份数据

    this.displayedData.forEach((candle, i) => {
      const open = candle[fields.open];
      const close = candle[fields.close];
      const x = i * barWidth + barWidth / 2;

      // 【优化】优先使用真实高低价，否则使用开收盘价计算
      let high = candle[fields.high] ?? Math.max(open, close);
      let low = candle[fields.low] ?? Math.min(open, close);

      // 计算Y坐标
      const yOpen = this._valueToY(open, this.dataBounds.kLine, height);
      const yClose = this._valueToY(close, this.dataBounds.kLine, height);
      const yHigh = this._valueToY(high, this.dataBounds.kLine, height);
      const yLow = this._valueToY(low, this.dataBounds.kLine, height);

      const isUp = close > open;
      ctx.strokeStyle = isUp ? this.config.kLine.upColor : this.config.kLine.downColor;
      ctx.fillStyle = isUp ? this.config.kLine.upColor : this.config.kLine.downColor;

      // 绘制影线（当有真实高低价时）
      if (fields.high && fields.low && candle[fields.high] !== undefined) {
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(x, yHigh);
        ctx.lineTo(x, yLow);
        ctx.stroke();
      }

      // 绘制实体（实心矩形）
      const bodyTop = Math.min(yOpen, yClose);
      const bodyHeight = Math.abs(yClose - yOpen) || 1;
      const kLineWidth = barWidth - this.config.barChart.barSpacing;
      ctx.fillRect(x - kLineWidth / 2, bodyTop, kLineWidth, bodyHeight);
    });
    ctx.restore();
  }

  _drawBarChartArea(ctx, y) {
    const height = this.config.barChart.height;
    this._drawGrid(ctx, y, height);
    if (this.dataBounds.barChart && this.config.showYAxisLabels) {
      this._drawYAxisLabels(ctx, y, height, this.dataBounds.barChart, 2);
    }

    ctx.save();
    ctx.beginPath();
    ctx.rect(this.config.x, y, this.config.width, height);
    ctx.clip();
    ctx.translate(this.config.x + this.offsetX, y);

    if (this.displayedData.length === 0) {
      ctx.restore();
      return;
    }

    const indicatorField = this.config.barChart.availableIndicators[this.currentBarChartIndicator].field;
    const barWidth = this.getBarWidth();
    const volumeBarWidth = this.config.barChart.barWidth;
    const spacing = (barWidth - volumeBarWidth) / 2;

    this.displayedData.forEach((candle, i) => {
      const value = candle[indicatorField];
      if (value === undefined || value === null) return;

      const x = i * barWidth + spacing;
      const isPositive = value >= 0;
      ctx.fillStyle = isPositive ? this.config.barChart.positiveColor : this.config.barChart.negativeColor;

      const barHeight = this._valueToY(Math.abs(value), this.dataBounds.barChart, height);
      const drawY = height - barHeight;

      ctx.fillRect(x, drawY, volumeBarWidth, barHeight);
    });
    ctx.restore();
  }

  _drawIndicatorArea(ctx, y) {
    const height = this.config.indicators.height;
    this._drawGrid(ctx, y, height);
    if (this.dataBounds.indicators && this.config.showYAxisLabels) {
      this._drawYAxisLabels(ctx, y, height, this.dataBounds.indicators, 2);
    }

    ctx.save();
    ctx.beginPath();
    ctx.rect(this.config.x, y, this.config.width, height);
    ctx.clip();
    ctx.translate(this.config.x + this.offsetX, y);

    if (this.displayedData.length === 0) {
      ctx.restore();
      return;
    }

    const barWidth = this.getBarWidth();

    this.config.indicators.visibleLines.forEach(lineKey => {
      const lineColor = this.config.indicators.lineColors[lineKey];
      if (!lineColor) return;

      ctx.strokeStyle = lineColor;
      ctx.lineWidth = this.config.indicators.lineWidth;
      ctx.beginPath();

      let hasData = false;
      this.displayedData.forEach((candle, i) => {
        const mappedKey = lineKey.toLowerCase().replace('ma', 'ma_hfq_');
        const value = candle[mappedKey];
        if (value !== undefined && value !== null) {
          const x = i * barWidth + barWidth / 2;
          const y = this._valueToY(value, this.dataBounds.indicators, height);
          if (!hasData) {
            ctx.moveTo(x, y);
            hasData = true;
          } else {
            ctx.lineTo(x, y);
          }
        }
      });
      if (hasData) ctx.stroke();
    });
    ctx.restore();
  }

  _drawGrid(ctx, y, height) {
    ctx.save();
    ctx.translate(this.config.x, y);
    ctx.strokeStyle = this.config.gridColor;
    ctx.lineWidth = 0.5;
    for (let i = 0; i <= 4; i++) {
      const gridY = (height / 4) * i;
      ctx.beginPath();
      ctx.moveTo(0, gridY);
      ctx.lineTo(this.config.width, gridY);
      ctx.stroke();
    }
    ctx.restore();
  }

  _drawYAxisLabels(ctx, y, height, bounds, decimals) {
    if (!bounds || typeof bounds.min === 'undefined' || typeof bounds.max === 'undefined') {
      return;
    }
    ctx.save();
    ctx.translate(this.config.x, y);
    ctx.fillStyle = this.config.textColor;
    ctx.font = '20px sans-serif';
    ctx.textAlign = 'right';
    for (let i = 0; i <= 4; i++) {
      const value = bounds.min + (bounds.max - bounds.min) * (1 - i / 4);
      ctx.fillText(value.toFixed(decimals), this.config.width - 5, (height / 4) * i + 3);
    }
    ctx.restore();
  }

  _drawCrosshair(ctx) {
    if (this.touch.x === null || this.touch.y === null) return;
    ctx.save();
    ctx.strokeStyle = this.config.crosshair.color;
    ctx.lineWidth = 0.5;
    ctx.setLineDash([5, 5]);
    ctx.beginPath();
    ctx.moveTo(this.config.x, this.touch.y);
    ctx.lineTo(this.config.x + this.config.width, this.touch.y);
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(this.touch.x, this.config.y);
    ctx.lineTo(this.touch.x, this.config.y + this.config.height);
    ctx.stroke();
    ctx.restore();
  }

  _drawTooltip(ctx) {
    if (!this.hoveredCandle) return;
    const fields = this.config.dataFields;
    const data = this.hoveredCandle;
    const barIndicatorName = this.config.barChart.availableIndicators[this.currentBarChartIndicator].name;
    const barIndicatorField = this.config.barChart.availableIndicators[this.currentBarChartIndicator].field;

    const lines = [
      `日期: ${data[fields.date]}`,
      `开: ${data[fields.open].toFixed(2)}`,
      `收: ${data[fields.close].toFixed(2)}`,
      `${barIndicatorName}: ${data[barIndicatorField].toFixed(2)}`,
    ];

    ctx.font = '12px sans-serif';
    const lineHeight = 18;
    const padding = 8;
    const boxWidth = Math.max(...lines.map(l => ctx.measureText(l).width)) + padding * 2;
    const boxHeight = lines.length * lineHeight + padding * 2;

    let tooltipX = this.touch.x + 15;
    let tooltipY = this.touch.y - boxHeight - 15;
    if (tooltipX + boxWidth > this.config.x + this.config.width) tooltipX = this.touch.x - boxWidth - 15;
    if (tooltipY < this.config.y) tooltipY = this.touch.y + 15;

    ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
    ctx.fillRect(tooltipX, tooltipY, boxWidth, boxHeight);
    ctx.fillStyle = '#ffffff';
    ctx.textAlign = 'left';
    lines.forEach((line, i) => {
      ctx.fillText(line, tooltipX + padding, tooltipY + padding + (i + 1) * lineHeight - 4);
    });
  }

  _showFloatingLabel(x, y, text) {
    this.floatingLabel.visible = true;
    this.floatingLabel.x = x;
    this.floatingLabel.y = y;
    this.floatingLabel.text = text;
    // 触发重绘
    if (this.externalRender) {
      this.externalRender();
    }
  }

  _hideFloatingLabel() {
    if (this.floatingLabel.visible) {
      this.floatingLabel.visible = false;
      // 触发重绘
      if (this.externalRender) {
        this.externalRender();
      }
    }
  }

  // --- 6. 辅助函数 ---
  getBarWidth() {
    return this.config.barChart.barWidth + this.config.barChart.barSpacing * 2;
  }

  _valueToY(value, bounds, areaHeight) {
    const range = bounds.max - bounds.min || 1;
    return (1 - (value - bounds.min) / range) * areaHeight;
  }

  // --- 7. 事件处理 ---
  handleTouchStart(touchX, touchY) {
    this.isDragging = true;
    this.hasMoved = false; // 重置移动状态
    this.touchStartTime = Date.now(); // 记录开始时间
    this.dragStartX = touchX;
    this.dragStartOffsetX = this.offsetX;
    this.touch.x = touchX;
    this.touch.y = touchY;
  }

  // 【新增】核心点击处理逻辑
  _handleClick(clickX, clickY) {
    // 1. 将屏幕绝对坐标转换为相对于图表区域的“视口坐标”
    const viewportX = clickX - this.config.x;
    const viewportY = clickY - this.config.y;

    // 如果点击在图表区域外，直接隐藏标签
    if (viewportX < 0 || viewportX > this.config.width || viewportY < 0 || viewportY > this.config.height) {
      this._hideFloatingLabel();
      return;
    }

    // 2. 【核心】使用视口坐标计算点击的是哪一根K线
    // 注意：这里不再减去 offsetX，因为我们是在视口内计算
    const barWidth = this.getBarWidth();
    const clickedIndex = Math.floor(viewportX / barWidth);

    // 3. 【核心】根据当前 offsetX 计算这个索引对应的数据在 displayedData 中的真实位置
    const firstVisibleIndex = Math.floor(-this.offsetX / barWidth);
    const actualDataIndex = firstVisibleIndex + clickedIndex;

    // 安全检查，防止索引越界
    if (actualDataIndex < 0 || actualDataIndex >= this.displayedData.length) {
      this._hideFloatingLabel();
      return;
    }

    const clickedData = this.displayedData[actualDataIndex];
    const fields = this.config.dataFields;

    // 4. 【核心】使用视口坐标判断点击的是哪个图表区域
    let currentAreaTop = 0;
    let labelText = '';
    let labelViewportY = 0; // 使用视口坐标

    // K线区域判断
    if (this.config.kLine.height > 0) {
      const kLineBottom = currentAreaTop + this.config.kLine.height;
      if (viewportY >= currentAreaTop && viewportY < kLineBottom) {
        // 【优化】获取高低价（优先真实值，否则用开收盘价计算）
        let high = clickedData[fields.high];
        let low = clickedData[fields.low];

        // 如果没有真实高低价，使用开收盘价计算
        if (high === undefined || low === undefined) {
          high = Math.max(clickedData[fields.open], clickedData[fields.close]);
          low = Math.min(clickedData[fields.open], clickedData[fields.close]);
        }

        // 构建显示文本（新增高低价）
        labelText = `${clickedData[fields.date]}\n开盘价:${clickedData[fields.open].toFixed(2)}\n收盘价: ${clickedData[fields.close].toFixed(2)}\n最高价:${high.toFixed(2)}\n最低价:${low.toFixed(2)}`;
        labelViewportY = currentAreaTop + this.config.kLine.height / 2;
      }
      currentAreaTop = kLineBottom;
    }

    // 指标区域判断
    if (!labelText && this.config.indicators.height > 0) {
      const indicatorBottom = currentAreaTop + this.config.indicators.height;
      if (viewportY >= currentAreaTop && viewportY < indicatorBottom) {
        const indicatorValues = this.config.indicators.visibleLines.map(lineKey => {
          const mappedKey = lineKey.toLowerCase().replace('ma', 'ma_hfq_');
          const value = clickedData[mappedKey];
          return `${lineKey}:${value ? value.toFixed(2) : 'N/A'}`;
        }).join('\n');
        labelText = `${clickedData[fields.date]}\n${indicatorValues}`;
        labelViewportY = currentAreaTop + this.config.indicators.height / 2;
      }
      currentAreaTop = indicatorBottom;
    }

    // 柱状图区域判断
    if (!labelText && this.config.barChart.height > 0) {
      const barChartBottom = currentAreaTop + this.config.barChart.height;
      if (viewportY >= currentAreaTop && viewportY < barChartBottom) {
        const indicatorField = this.config.barChart.availableIndicators[this.currentBarChartIndicator].field;
        const value = clickedData[indicatorField];
        const indicatorName = this.config.barChart.availableIndicators[this.currentBarChartIndicator].name;
        labelText = `${clickedData[fields.date]}\n${indicatorName}: ${value ? value.toFixed(2) : 'N/A'}`;
        labelViewportY = currentAreaTop + this.config.barChart.height / 2;
      }
    }

    // 5. 显示或隐藏标签
    if (labelText) {
      // 将视口坐标转换回屏幕绝对坐标来显示标签
      const absoluteLabelY = this.config.y + labelViewportY;
      this._showFloatingLabel(clickX, absoluteLabelY, labelText);
    } else {
      this._hideFloatingLabel();
    }
  }

  handleTouchMove(touchX, touchY) {
    if (!this.isDragging) return false;

    const deltaX = Math.abs(touchX - this.dragStartX);
    const deltaY = Math.abs(touchY - this.dragStartY);

    // 【新增】如果移动距离超过阈值，则认为是滑动，而不是点击
    const MOVE_THRESHOLD = 5; // 5像素的移动阈值
    if (deltaX > MOVE_THRESHOLD || deltaY > MOVE_THRESHOLD) {
      this.hasMoved = true;
    }

    // 只有在已经判定为滑动时，才更新图表偏移
    if (this.hasMoved) {
      const barWidth = this.getBarWidth();
      const totalWidth = this.displayedData.length * barWidth;
      const maxOffset = Math.min(0, this.config.width - totalWidth);
      const newOffsetX = Math.max(maxOffset, Math.min(0, this.dragStartOffsetX + (touchX - this.dragStartX)));

      if (newOffsetX !== this.offsetX) {
        this.offsetX = newOffsetX;
        this._updateHoveredCandle(touchX);
        return true; // 返回true表示需要重绘
      }
    }
    return false;
  }

  handleTouchEnd(touchX, touchY) {
    const touchEndTime = Date.now();
    const touchDuration = touchEndTime - this.touchStartTime;

    console.log('handleTouchEnd')

    // 【核心逻辑】判断是否为一次有效的点击
    // 条件：移动距离很小 && 持续时间很短
    const CLICK_DURATION_THRESHOLD = 300; // 300毫秒
    const MOVE_THRESHOLD = 5; // 5像素

    if (!this.hasMoved && touchDuration < CLICK_DURATION_THRESHOLD) {
      // 这是一个点击事件
      this._handleClick(touchX, touchY);
    }

    // 重置状态
    this.isDragging = false;
    this.hasMoved = false;
    this.touch.x = null;
    this.touch.y = null;
  }

  _updateHoveredCandle(touchX) {
    const oldHovered = this.hoveredCandle;
    this.hoveredCandle = null;
    const barWidth = this.getBarWidth();
    const relativeX = touchX - this.config.x - this.offsetX;

    if (relativeX >= 0) {
      const index = Math.floor(relativeX / barWidth);
      if (index >= 0 && index < this.displayedData.length) {
        this.hoveredCandle = this.displayedData[index];
      }
    }
    return oldHovered !== this.hoveredCandle;
  }

  // --- 8. 外部API ---
  setVisibleIndicators(visibleLines) {
    this.config.indicators.visibleLines = visibleLines.filter(line =>
      this.config.indicators.allLines.includes(line)
    );
    this._calculateDataBounds();
  }

  setBarChartIndicator(indicatorKey) {
    if (!this.config.barChart.availableIndicators[indicatorKey]) {
      console.error(`Indicator "${indicatorKey}" is not available.`);
      return;
    }
    this.currentBarChartIndicator = indicatorKey;
    this._calculateDataBounds();
  }

  /**
   * 获取当前正在屏幕上显示的数据量
   * @returns {number} displayedData 数组的长度
   */
  getDisplayedDataCount() {
    return this.displayedData.length;
  }

  /**
   * 【公共方法】清除浮动标签
   * 可以在页面逻辑中调用，例如在追加新数据后
   */
  clearFloatingLabel() {
    this._hideFloatingLabel();
  }

  //销毁组件
  destroy() {
    this.allData = [];
    this.displayedData = [];
  }
}
